﻿Imports System.Windows.Forms
Imports System.Drawing
Imports log4net

Namespace Display

    ''' <summary>
    ''' This class handles the basic layout of the main Pager Display window.
    ''' 
    ''' The class as two main functions. The first is to determine the initial
    ''' geometry for the main Pager Display window depending on type of Monitor
    ''' detected.  The second is to dynamically determine geometry and position 
    ''' of the individual GUI components such as the Border, LED Panel and the
    ''' individual LED Status Light Elements.
    ''' 
    ''' Additionally this class is responsible for creating the Status LED 
    ''' GUI Elements and calculating the correct font size to use. 
    ''' </summary>
    ''' <remarks>Only one instance of this class should be instantiated.</remarks>
    Public Class LayoutManager

        ' -------------------------------------------------------------------------
        ' Shared variables
        ' -------------------------------------------------------------------------
        '
        'Logger
        Private Shared sLog As ILog

        ' -------------------------------------------------------------------------
        ' Member variables
        ' -------------------------------------------------------------------------
        '
        ' The Control String is used in calculating the appropriate font size.
        Private Const CONTROL_STRING As String = "8WO"

        Private mIsUsingSecondary As Boolean = False

        ' Holds the instance of the main Pager Display window
        Private mPagerDisplay As PagerDisplay

        Private mDisplayMargin As Double = 0

        ' Hold the instance of the System LED GUI Element
        Private mSystemLED As Shape

        ' Hold the instance of the Network LED GUI Element
        Private mNetworkLED As Shape

        ' Hold the instance of the Page LED GUI Element
        Private mPageLED As Shape


        ' -------------------------------------------------------------------------
        ' Property Definitions
        ' -------------------------------------------------------------------------
        '
        ''' <summary>
        ''' The System Status LED UI Element
        ''' </summary>
        ''' <returns>A Shape representing the LED on the Display</returns>
        ''' <remarks>The System LED displays the over all status of the Pager System.</remarks>
        Public ReadOnly Property SystemLED As Shape
            Get
                Return mSystemLED
            End Get
        End Property

        ''' <summary>
        ''' The Network Status LED UI Element
        ''' </summary>
        ''' <returns>A Shape representing the LED on the Display</returns>
        ''' <remarks>The Network LED displays the over all status of the Network.</remarks>
        Public ReadOnly Property NetworkLED As Shape
            Get
                Return mNetworkLED
            End Get
        End Property

        ''' <summary>
        ''' The Page Status LED UI Element
        ''' </summary>
        ''' <returns>A Shape representing the LED on the Display</returns>
        ''' <remarks>The Page LED displays the over all status of the the Page codes read from file.</remarks>
        Public ReadOnly Property PageLED As Shape
            Get
                Return mPageLED
            End Get
        End Property

        ''' <summary>
        ''' Indicates if PagerDisplay is using an External (Non-Primary) Monitor
        ''' </summary>
        ''' <returns>A Boolean, TRUE if using External (Secondary) Monitor, otherwise FASLE </returns>
        ''' <remarks>The Page LED displays the over all status of the the Page codes read from file.</remarks>
        Public ReadOnly Property IsUsingExternal As Boolean
            Get
                Return mIsUsingSecondary
            End Get
        End Property



        ' -------------------------------------------------------------------------
        ' Main Code
        ' -------------------------------------------------------------------------

        ''' <summary>
        ''' Constructor.
        ''' </summary>
        ''' <param name="display">The PagerDisplay window to manage layout of.</param>
        Public Sub New(ByRef display As PagerDisplay)
            mPagerDisplay = display
            sLog = LogManager.GetLogger(Me.GetType)

            createLEDs()
        End Sub

        ''' <summary>
        ''' Performs initial configuration and layout of the Pager Display Window
        ''' </summary>
        ''' <remarks>Should be called once to perform initital setup.  Subsequent layouts
        ''' should be performed through the layoutPagerDisplay() method.</remarks>
        Public Sub configureMainDisplay()

            ' Determine main window geometry
            determineDisplayGeometry()
            ' Perform initial window layout
            layoutPagerDisplay()

        End Sub

        ''' <summary>
        ''' Determines what the initial Display window geometry should be.  The preference is
        ''' to locate an external, non-primary monitor on which to display the window full
        ''' screen without any border or control decorations. As an Overhead Paging System 
        ''' this is considered the normal operating mode.  If only the Primary monitor/screen
        ''' is found the system uses a standard window as defined in the XAML.
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub determineDisplayGeometry()

#If DEBUG Then
            Debug.WriteLine("DEBUG MODE: Will not use external display.")
            mIsUsingSecondary = False
#Else

        ' Find the first no-primary monitor which should be the external monitor
        Dim display As Screen
        For Each display In Screen.AllScreens
            ' If an external display is found then open full screen on that display
            If Not display.Primary Then
                mPagerDisplay.Hide()
                mPagerDisplay.WindowStyle = WindowStyle.None
                mPagerDisplay.Top = display.Bounds.Top
                mPagerDisplay.Left = display.Bounds.Left
                mPagerDisplay.Height = display.Bounds.Height
                mPagerDisplay.Width = display.Bounds.Width
                mPagerDisplay.WindowState = FormWindowState.Maximized
                mPagerDisplay.Show()
                mIsUsingSecondary = True
                Exit For
            End If
        Next
#End If

            ' Register with resize events so we can perform necessary layouts.
            ' This should only be required if not running in the normal external monitor mode.
            AddHandler mPagerDisplay.SizeChanged, AddressOf windowSizeChangeHandler

        End Sub

        ''' <summary>
        ''' Performs a (re)layout of the Pager Display window.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub layoutPagerDisplay()

            Dim screenWidth As Double = mPagerDisplay.ActualWidth
            Dim screenHeight As Double = mPagerDisplay.ActualHeight

            Dim rctBorder As Shapes.Rectangle = mPagerDisplay.FindName("Border")
            Dim spnLEDArea As StackPanel = mPagerDisplay.FindName("LEDPanel")

            Debug.WriteLine("Screen Width:  " & screenWidth)
            Debug.WriteLine("Screen Hieght: " & screenHeight)

            Dim borderThickness As Double = screenHeight * 0.02
            rctBorder.StrokeThickness = borderThickness
            '' Remove border from layout
            rctBorder.Visibility = Visibility.Collapsed

            spnLEDArea.Height = borderThickness * 2
            spnLEDArea.Width = screenWidth * 0.25

            Dim ledBorder = spnLEDArea.Height * 0.1
            Dim ledHeight = spnLEDArea.Height - (2 * ledBorder)
            Dim ledWidth = (spnLEDArea.Width - (3 * ledBorder)) / 2

            sizeLED(mSystemLED, ledBorder, ledHeight, ledWidth)
            sizeLED(mNetworkLED, ledBorder, ledHeight, ledWidth)
            'sizeLED(mPageLED, ledBorder, ledHeight, ledWidth)

            spnLEDArea.Children.Clear()
            spnLEDArea.Children.Add(mSystemLED)
            spnLEDArea.Children.Add(mNetworkLED)
            'spnLEDArea.Children.Add(mPageLED)

            ' Recalculating Font Size can take a few seconds so push it into the background
            mPagerDisplay.Dispatcher.BeginInvoke(New Action(AddressOf determineFontSize))

        End Sub

        Private Sub sizeLED(ByRef led As Shape,
                            ByVal border As Double,
                            ByVal height As Double,
                            ByVal width As Double)
            led.Margin = New Thickness(border, border, 0, border)
            led.Height = height
            led.Width = width
            led.StrokeThickness = border * 0.25

        End Sub

        Private Sub windowSizeChangeHandler(ByVal sender As Object, ByVal e As SizeChangedEventArgs)
            layoutPagerDisplay()
        End Sub

        Private Sub createLEDs()
            mSystemLED = createLED()
            mNetworkLED = createLED()
            mPageLED = createLED()
        End Sub

        Private Function createLED() As Shapes.Rectangle
            Dim r = New Shapes.Rectangle()
            r.Stroke = New SolidColorBrush(Colors.Black)
            Return r
        End Function

        ''' <summary>
        ''' Dynamically calculate the new Font size based on current window geometry of the
        ''' main Pager Diplay
        ''' </summary>
        Private Sub determineFontSize()
            Dim fontFamily As String = mPagerDisplay.PageBlock.FontFamily.ToString
            Dim fontSize As Integer = 10
            Dim stepSize As Integer = 100
            Dim stringSize As SizeF = getStringSize(CONTROL_STRING, fontFamily, fontSize)

            sLog.Debug("Calculating font size.")

            ' First size up the font by increments of 100
            Do
                fontSize = fontSize + stepSize
                stringSize = getStringSize(CONTROL_STRING, fontFamily, fontSize)
                Debug.WriteLine("Considering: " & fontSize & "(" & stringSize.ToString & ")")
            Loop While (isFontTooSmall(stringSize))

            'once we've stepped over the size limt come down in increments of 10
            stepSize = 10
            Do
                fontSize = fontSize - stepSize
                stringSize = getStringSize(CONTROL_STRING, fontFamily, fontSize)
                Debug.WriteLine("Considering: " & fontSize & "(" & stringSize.ToString & ")")
            Loop While (isFontTooLarge(stringSize))

            ' Set the newly caluclate font size on the Page Code textblock UI Element
            sLog.Debug("Optimal font size calculated is " & fontSize)
            mPagerDisplay.PageBlock.FontSize = fontSize

        End Sub

        Private Function isFontTooLarge(ByVal stringSize As SizeF) As Boolean

            Return (stringSize.Width > getMaxWidth() Or
                     stringSize.Height > getMaxHeight())

        End Function

        Private Function isFontTooSmall(ByVal stringSize As SizeF) As Boolean

            Return (stringSize.Width < getMaxWidth() And
                     stringSize.Height < getMaxHeight())

        End Function

        Private Function getMaxWidth() As Double
            'The numeric multiplier offsets an issue with the Graphics.MeasureString
            '  method that calculates text sizing a bit smaller than when drawn.  
            '  To offset this we allow for more drawing width then we really have.
            Return mPagerDisplay.ActualWidth * (1.25 - mDisplayMargin)
        End Function

        Private Function getMaxHeight() As Double
            'The numeric multiplier offsets an issue with the Graphics.MeasureString
            '  method that calculates text sizing a bit smaller than when drawn.  
            '  To offset this we allow for more drawing height then we really have.
            Return mPagerDisplay.ActualHeight * (1.5 - mDisplayMargin)
        End Function

        Private Function getStringSize(ByVal text As String,
                               ByVal fontFamily As String,
                               ByVal fontSize As Integer) As SizeF

            Dim img As New Bitmap(1, 1)
            Dim g As Graphics = Graphics.FromImage(img)
            g.PageUnit = GraphicsUnit.Pixel
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias

            Dim f As New Font(fontFamily, fontSize)
            Dim stringSize As SizeF = g.MeasureString(text, f, 2000, StringFormat.GenericTypographic)
            g.Dispose()
            img.Dispose()
            f.Dispose()
            Return stringSize

        End Function

    End Class

End Namespace
